package com.atguigu.gulimall.search.service.serviceImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to_and_vo.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.GulimallElasticConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.AttrResponseVo;
import com.atguigu.gulimall.search.vo.BrandEntity;
import com.atguigu.gulimall.search.vo.SearchParam;
import com.atguigu.gulimall.search.vo.SearchResult;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Resource
    private ProductFeignService productFeignService;

    @Resource
    private RestHighLevelClient restHighLevelClient;

    /**
     * 去ES中进行检索
     * @param searchParam
     * @return
     */
    @Override
    public SearchResult search(SearchParam searchParam) {
        System.out.println("请求参数："+searchParam);
        SearchResult result = null;

        //1.动态构建出查询需要的DSL语句
        //1.1 先准备检索请求
//        SearchRequest searchRequest = new SearchRequest();
        //因为我们的DSL语句十分复杂，我们需要单独调一个方法帮助我们构建检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            //1.2 执行检所请求
            SearchResponse response = restHighLevelClient.search(searchRequest, GulimallElasticConfig.COMMON_OPTIONS);

            //1.3 分析相应数据并封装成我们需要的格式
            result = buildSearchResult(response,searchParam);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Object json = JSON.toJSON(result);
        System.out.println("查询到的结果为："+json);
        return result;
    }

    /**
     * 帮助我们将ES中查询到的结果  构建成结果数据
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response,SearchParam searchParam) {
        SearchResult result = new SearchResult();
        SearchHits hits = response.getHits();
        SearchHit[] hitsHits = hits.getHits();
        List<SkuEsModel> products = new ArrayList<>();

        if(hitsHits != null && hitsHits.length > 0){
            for (SearchHit hit : hitsHits) {
                String source = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(source, new TypeReference<SkuEsModel>() {});
                if(!StringUtils.isEmpty(searchParam.getKeyword())){
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String highLight = skuTitle.getFragments()[0].string();
                    skuEsModel.setSkuTitle(highLight);
                }
                products.add(skuEsModel);
            }
        }
//        //1. 返回的所有查询到的商品
        result.setProducts(products);
//-----------------------------以上可以从查询信息中获取到---------------------------------------------------
        Aggregations aggregations = response.getAggregations();

//        //2. 当前所有商品涉及到的所有分类信息
        List<SearchResult.CatalogVo> catalogs = new ArrayList<>();

        ParsedLongTerms catalog_agg = aggregations.get("catalog_agg");
        List<? extends Terms.Bucket> catalogAggBuckets = catalog_agg.getBuckets();
        if(catalogAggBuckets != null && catalogAggBuckets.size() > 0){
            for (Terms.Bucket bucket : catalogAggBuckets) {
                SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
                //得到分类名字
                catalogVo.setCatalogId((Long) bucket.getKey());
                Aggregations aggregations1 = bucket.getAggregations();
                ParsedStringTerms catalog_name_agg = aggregations1.get("catalog_name_agg");
                //得到分类id
                String name = catalog_name_agg.getBuckets().get(0).getKeyAsString();
                catalogVo.setCatalogName(name);
                catalogs.add(catalogVo);
            }
        }
        result.setCatalogs(catalogs);
//        result.setAttrs();
//        //3. 当前所有商品涉及到的所有属性信息
        List<SearchResult.AttrVo> attrs = new ArrayList<>();
        ParsedNested attr_agg = aggregations.get("attr_agg");
        Aggregations aggregations1 = attr_agg.getAggregations();
        ParsedLongTerms attrIdAgg = aggregations1.get("attr_id_agg");
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        if(attrIdAggBuckets != null && attrIdAggBuckets.size() > 0){
            for (Terms.Bucket bucket : attrIdAggBuckets) {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                //得到属性id
                attrVo.setAttrId((Long) bucket.getKey());
                Aggregations bucketAggregations = bucket.getAggregations();
                ParsedStringTerms attr_name_agg = bucketAggregations.get("attr_name_agg");
                //得到属性名字
                String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
                attrVo.setAttrName(attrName);
                ParsedStringTerms attr_value_agg = bucketAggregations.get("attr_value_agg");
                //得到属性值
                List<String> attrValues = attr_value_agg.getBuckets().stream().map((item) -> {
                    String attrValue = item.getKeyAsString();
                    return attrValue;
                }).collect(Collectors.toList());
                attrVo.setAttrValue(attrValues);
                attrs.add(attrVo);
            }
        }

        result.setAttrs(attrs);

//        result.setBrands();
//        //4. 当前所有商品涉及到的所有品牌信息
        List<SearchResult.BrandVo> brands = new ArrayList<>();
        ParsedLongTerms brand_agg = aggregations.get("brand_agg");
        List<? extends Terms.Bucket> brandAggBuckets = brand_agg.getBuckets();
        if(brandAggBuckets != null && brandAggBuckets.size() > 0){
            for (Terms.Bucket bucket : brandAggBuckets) {
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                //得到品牌id
                brandVo.setBrandId((Long) bucket.getKey());
                Aggregations bucketAggregations = bucket.getAggregations();
                ParsedStringTerms brandImgAgg = bucketAggregations.get("brand_img_agg");
                String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                ParsedStringTerms brandNameAgg = bucketAggregations.get("brand_name_agg");
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandImg(brandImg);
                brandVo.setBrandName(brandName);
                brands.add(brandVo);
            }
        }
        result.setBrands(brands);
//        result.setCatalogs();



//-----------------------------以上可以从聚合信息中获取到---------------------------------------------------
//        //5. 分页信息  页码
        if(searchParam.getPageNum() != null){
            result.setPageNum(searchParam.getPageNum());
        }

//        //5. 分页信息  总记录数
        long value = hits.getTotalHits().value;
        result.setTotal(value);
//        //5. 分页信息  总页码--------我们需要计算得到: 总记录数 / 每页最大值
        int totalPages = ((int)value % EsConstant.PRODUCT_PAGESIZE == 0) ? ((int)value / EsConstant.PRODUCT_PAGESIZE) : ((int)value / EsConstant.PRODUCT_PAGESIZE + 1);
        result.setTotalPages(totalPages);
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);


        //最后我们构建面包屑导航功能
        List<SearchResult.NavVo> navs = new ArrayList<>();
        List<String> paramAttrs = searchParam.getAttrs();
        if(paramAttrs != null && paramAttrs.size() > 0){
            List<SearchResult.NavVo> navVoList = paramAttrs.stream().map((item) -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                //1.分析每个attrs传过来的查询参数值，构建面包屑vo
//            attrs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏
                String[] split = item.split("_");
                //由于我们只能 获取到属性的id，因此我们还需要去商品服务中，去查对应的名字是什么

                R attrInfo = productFeignService.attrInfo(Long.parseLong(split[0]));
                result.getAttrIds().add(Long.parseLong(split[0]));
                if(attrInfo.getCode() == 0){    //正常返回
                    AttrResponseVo vo = attrInfo.getData("attr", new TypeReference<AttrResponseVo>() {
                    });
                    navVo.setNavName(vo.getAttrName());
                }else {
                    navVo.setNavName(split[0]);
                }
                navVo.setNavValue(split[1]);

                //2.取消了面包屑以后，我么们要跳转到哪个地方，因为我们
                //添加了面包屑后会将对应的参数拼接到url上面，因此点击取消
                //面包屑就是把对应的url地址替换掉,我们需要获取到所有的查询条件
                String replace = replaceQueryString(searchParam, item,"attrs");
                navVo.setLink("http://search.gulimall.com/list.html?"+replace);
                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(navVoList);
        }

        //品牌，分类
        if(searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0){
            List<SearchResult.NavVo> navVos = result.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setNavName("品牌");
            //TODO 远程查询所有品牌，并且品牌可能有多个
            R brandInfo = productFeignService.brandInfo(searchParam.getBrandId());
            if(brandInfo.getCode() == 0){
                List<BrandEntity> brand = brandInfo.getData("brand", new TypeReference<List<BrandEntity>>() {
                });
                StringBuffer stringBuffer = new StringBuffer();
                String replace = "";
                for (BrandEntity entity : brand) {
                    String name = entity.getName();
                    stringBuffer.append(name+";");
                    replace = replaceQueryString(searchParam, entity.getBrandId().toString(), "brandId");

                }
                navVo.setNavValue(stringBuffer.toString());
                navVo.setLink("http://search.gulimall.com/list.html?"+replace);
                navVos.add(navVo);

            }

        }


        return result;
    }

    private String replaceQueryString(SearchParam searchParam, String item,String key) {
        String oldQuery = searchParam.getQueryString();
        String encode = null;
        try {
            encode = URLEncoder.encode(item, "UTF-8");
            encode = encode.replace("+","%20");//浏览器对空格编码和java不一样
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String replace = oldQuery.replace("&"+key+"=" + encode, "");
        return replace;
    }

    /**
     * 帮助我们构建检索请求
     * #模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存），排序，分页，高亮，聚合分析
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//用来构建DSL语句

        //关于查询部分：模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存）
        //1.构建bool - query :旗下有两个查询：must和filter
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1 关于must：
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
        }
        //1.2 关于filter：
        //catalogId
        if (searchParam.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
        }
        //brandId
        if (searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandId()));
        }
        //attrs
        if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0) {

            ///寸&aattrs=1_5寸:8ttrs=2_16G:8G
            for (String attr : searchParam.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] s = attr.split("_");
                String attrId = s[0];//检索的属性id
                String[] values = s[1].split(":");  //检索出的属性对应的不同的值
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", values));
                //每一个必须都得生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }

        }
        //hasStock
        if (searchParam.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }
        //skuPrice  价格区间
        if (!StringUtils.isEmpty(searchParam.getSkuPrice())) {
            //skuPrice=1_500/_500/500_
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] split = searchParam.getSkuPrice().split("_");
            System.err.println("价格区间："+Arrays.toString(split));
            List<String> strings = Arrays.asList(split);
            strings.forEach((i) -> {
                int index = strings.indexOf(i);
                System.err.println("split[" + index + "] = " + i);
            });
//            if(split != null && split.length > 0){
//                if(split[0].equals("")){
//                    rangeQuery.gte("0").lte(split[1]);
//                }else if(split[0].equals("")){
//                    rangeQuery.gte("0");
//                }else{
//                    rangeQuery.gte(split[0]).lte(split[1]);
//                }
            if(split.length > 0){
                if (!searchParam.getSkuPrice().startsWith("_") && !searchParam.getSkuPrice().endsWith("_")) {
                    //区间
                    rangeQuery.gte(split[0]).lte(split[1]);
                } else if (searchParam.getSkuPrice().startsWith("_")) {
                    rangeQuery.from(null).to(split[1]);
                } else if (searchParam.getSkuPrice().endsWith("_")) {
                    rangeQuery.from(split[0]);
                }
            }




            boolQuery.filter(rangeQuery);
        }



        //把以前的所有条件都拿来进行封装，进而统一查询
        sourceBuilder.query(boolQuery);

        //排序，分页，高亮
        //a)排序;  sort=hotScore_asc/desc
        if(!StringUtils.isEmpty(searchParam.getSort())){
            String sort = searchParam.getSort();
            String[] s = sort.split("_");
            SortOrder order = SortOrder.ASC.toString() .equalsIgnoreCase(s[1])? SortOrder.ASC : SortOrder.DESC;
            sourceBuilder.sort(s[0],order);
        }
        //b)分页： pageSize:5
        //  pageNum:1  from:0  size:5  [0,1,2,3,4]
        // pageNum:2  from:5   size:5
        //from = (pageNum-1)*size
        if(searchParam.getPageNum() != null){
            sourceBuilder.from((searchParam.getPageNum()-1) * EsConstant.PRODUCT_PAGESIZE);
            sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        }


        //c)高亮： 我们只有在传递了keyword的时候才有必要进行高亮处理
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("<b style='color:red'>");
            builder.postTags("</b>");
            sourceBuilder.highlighter(builder);
        }


        //聚合分析
        //TODO 1.品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId");
        brand_agg.size(10);

        //1.1 品牌聚合的子聚合
        TermsAggregationBuilder brand_name_agg = AggregationBuilders.terms("brand_name_agg");
        brand_name_agg.field("brandName");
        brand_name_agg.size(1);
        TermsAggregationBuilder brand_img_agg = AggregationBuilders.terms("brand_img_agg");
        brand_img_agg.field("brandImg");
        brand_img_agg.size(1);
        TermsAggregationBuilder subAggregation1 = brand_agg.subAggregation(brand_name_agg);
        TermsAggregationBuilder subAggregation2 = brand_agg.subAggregation(brand_img_agg);
        sourceBuilder.aggregation(brand_agg);

        //TODO 2.分类聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId");
        catalog_agg.size(20);
        TermsAggregationBuilder catalog_name_agg = AggregationBuilders.terms("catalog_name_agg");
        catalog_name_agg.field("catalogName");
        catalog_name_agg.size(1);
        TermsAggregationBuilder subAggregation = catalog_agg.subAggregation(catalog_name_agg);
        sourceBuilder.aggregation(catalog_agg);


        //TODO 3. 属性聚合
        NestedAggregationBuilder nested = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(10);
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(10));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(10));
        nested.subAggregation(attr_id_agg);
        sourceBuilder.aggregation(nested);

//        sourceBuilder.size(20);
        String s = sourceBuilder.toString();
        System.out.println("构建的DSL："+s);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);

        return searchRequest;
    }
}
